#include "FileSystem.h"

using namespace std;
using __gnu_cxx :: hash_map;

/* public */

FileSystem :: FileSystem(const string &path){

	string files_path;
	this->repo_path = path;
	this->db_path = this->repo_path + "/" + DATABASE_FILE;
	this->files_path = this->repo_path + "/" + FILES_DIR + "/";
	this->database = new Database(this->db_path, this->files_path);
					
	this->tags = this->database->get_tags();
	if (tags == NULL){
#ifdef DEBUG
		cout << "FileSystem could not be created; database not operational;" << endl;
#endif
		exit(-1);
	};	
	
};

int FileSystem :: get_stat(const char *path, struct stat *stbuf){
#ifdef DEGUB
	cout << "[FileSystem :: get_stat ] Received " << path << endl;
#endif
	string new_path(path);
	return this->get_stat(new_path, stbuf);
};

int FileSystem :: get_stat(const string &path, struct stat *stbuf){
	vector<string> split_path;
	split(path, split_path);
	hash_map<string, Tag> :: iterator j;
	set<Tag *> passed;
	unsigned i = 0;
	
	for (i = 0; (i < split_path.size()) && (split_path[i] != FILES_DIR); i++){
		if (((j = this->tags->find(split_path[i])) == this->tags->end()) || 
			(passed.find(&j->second) != passed.end()))
			return -1;
		passed.insert(&j->second);
	}
	if ((i == split_path.size()) || (i + 1 == split_path.size())){
		/* getting stats for root or path with directories only */
		if (split_path.size() == 0)
			stbuf->st_nlink = ROOT_DIR_NLINK;
		else
			stbuf->st_nlink = DEFAULT_DIR_NLINK;	
		stbuf->st_mode = S_IFDIR | RIGHTS;
		return 0;
	}
	else if (i + 2 == split_path.size())
		/* direct descendant of Files directory, unambiguous file */
		return this->get_direct_stat(split_path[i + 1], passed, stbuf);
	else if (i + 3 == split_path.size())
		/* not direct descendant of Files directory, ambiguous file */
		return this->get_deep_stat(split_path[i + 2], passed, stbuf);
	else
		return -1;
	
};

vector<string>* FileSystem :: get_children(const string &path){

	vector<string> split_path; 
	set<Tag *> passed;
	hash_map<string, Tag> :: iterator j;
	unsigned i = 0;

	split(path, split_path);
	for (i = 0; (i < split_path.size()) && (split_path[i] != FILES_DIR); i++){
		if (((j = this->tags->find(split_path[i])) == this->tags->end()) ||
			(passed.find(&j->second) != passed.end()))
			return NULL;
		passed.insert(&j->second);
	};
	if (i == split_path.size())
		/* path contains only tags */
		return get_tag_children(passed);
	else if (i + 1 == split_path.size())
		return get_direct_children(passed);
	else if (i + 2 == split_path.size())
		return get_deep_children(split_path[i + 1], passed);
	else
		return NULL;

};

int FileSystem :: add_dir(const string &path){
	
	vector<string> split_path; 
	split(path, split_path);
	
	if (split_path.size() == 0)
		return -1;
	string name = split_path[split_path.size() - 1];
	return this->add_tag(name);
	
};

int FileSystem :: add_file(const string &path){

	Tag tag;
#ifdef DEBUG
	cout << "[FileSystem :: add_file] Adding file " << path << endl;
#endif
	vector<Tag> tags;
	string name;
	
	if ((this->get_tags(path, tags) != 0) || (this->get_file_name(path, name) != 0))
		return -1;
	File file(-1, name, path);
	database->insert(file);
	/* tag the file */
	for (unsigned i = 0; i < tags.size(); i++) {
		tag = Tag(-1, tags[i].get_name());
		database->add(file, tag);
	}
	
	return 0;
};

int FileSystem :: write_file(const string &path, const char *buf, size_t size, off_t offset) {
	
#ifdef DEBUG
	cout << "[FileSystem :: write_file] Writing to file " << path << endl;
#endif
	int ret = 0, fd = 0;
	string real_path;
	if (this->get_real_path(path, real_path) != 0)
		return -1;
	if ((fd = open(real_path.c_str(), O_WRONLY)) < 0)
		return -errno;
	ret = pwrite(fd, buf, size, offset);
	close(fd);

	if (ret >= 0)
		return ret; 
	else
		return -errno;
		
};
	
int FileSystem :: read_file(const string &path, char *buf, size_t size, off_t offset) {
			    
	string real_path; 
	if (this->get_real_path(path, real_path) != 0)
		return -1;
	int ret = 0, fd = 0;
	
	if ((fd = open(real_path.c_str(), O_RDONLY)) < 0)
		return -errno;
	ret = pread(fd, buf, size, offset);
	close(fd);
	return ret;

};

int FileSystem :: utime_file(const string &path, const struct timespec tv[2]) {

	string real_path; 
	if (this->get_real_path(path, real_path) != 0)
		return -1;
	int ret = 0;
	struct utimbuf time_buf;
	
	time_buf.actime = tv[0].tv_sec;
	time_buf.modtime = tv[1].tv_sec;
	if ((ret = utime(real_path.c_str(), &time_buf)) < 0)
		return -errno;
	
	return ret;

};

int FileSystem :: del_dir(const string &path){

	vector<string> split_path;
	if ((split(path, split_path) != 0) || (split_path.size() == 0))
		return -1;
	string name = split_path[split_path.size() - 1];
	return this->del_tag(name);
	
};

int FileSystem :: delete_file(const char *path){
	string new_path(path);
	return this->delete_file(new_path);
};

int FileSystem :: delete_file(const string &path){
	
	string name;
	vector<Tag> tags;
	
	if ((this->get_file_name(path, name) != 0) || (this->get_tags(path, tags)))
		return -1;	

	auto_ptr<File> file(this->database->get_file(tags, name.c_str()));
	if (file.get() == 0)
		return -1;
	auto_ptr< vector<Tag> > file_tags(this->database->get_tags(*file));
	if (file_tags.get() == 0)
		return -1;
	if (file_tags->size() == 0)
		database->del(*file);
	else
		for (vector<Tag> :: iterator i = tags.begin(); i != tags.end(); i++)
			database->del(*file, *i);
	return 0;
	
};

FileSystem :: ~FileSystem(){
	delete this->database;
	delete this->tags;
};

/* private */

int FileSystem :: get_direct_stat(const string &name, const set<Tag *> &passed, struct stat *stbuf){

	/* aquiring files */
	vector<Tag> tags;
	for (set <Tag *> :: iterator j = passed.begin(); j != passed.end(); j++)
		tags.push_back(**j);
	auto_ptr< vector<File> > files(database->get_files(tags));
	if (files.get() == 0)
		return -1;
	
	/* finding file */
	vector<File> :: iterator i;
	for (i = files->begin(); (i != files->end()) && (i->get_name() != name); i++);
	if (i == files->end())
		/* file not found */
		return -1;
	else if ((i + 1 == files->end()) || ((i + 1)->get_name() != name)){
		/* non ambiguous file */
		stbuf->st_mode = S_IFREG | RIGHTS;
		string path = this->repo_path + "/" + FILES_DIR + "/" + to_string(i->get_id());
		stat(path.c_str(), stbuf);
	}
	else{
		/* ambiguous file, represented as a directory */
		stbuf->st_mode = S_IFDIR | RIGHTS;
		stbuf->st_nlink = DEFAULT_DIR_NLINK;
	};
	return 0;

};

int FileSystem :: get_deep_stat(const string &name, const set<Tag *> &passed, struct stat *stbuf){
	
	/* aquiring files */
	vector<Tag> tags;
	for (set <Tag *> :: iterator i = passed.begin(); i != passed.end(); i++)
		tags.push_back(**i);
	vector<File> *files = database->get_files(tags);
	
	/* finding file */
	for (vector<File> :: iterator i = files->begin(); i != files->end(); i++){
		if (to_string(i->get_id()) + string(".") + i->get_name() == name){
			stbuf->st_mode = S_IFREG | RIGHTS;
			string path = this->repo_path + "/" + FILES_DIR + "/" + to_string(i->get_id());
			stat(path.c_str(), stbuf);
			return 0;
		};
	};
	/* file not found */
	return -1;
	
};

vector<string>* FileSystem :: get_tag_children(const set<Tag *> &passed){
	
	hash_map<string, Tag>::iterator i;
	vector<string> *result = new vector<string>;
	
	for (i = this->tags->begin(); i != this->tags->end(); i++)
		if (passed.find(&i->second) == passed.end())
			result->push_back(i->second.get_name());
	result->push_back(FILES_DIR);
	return result;
		
};

vector<string>* FileSystem :: get_direct_children(const set<Tag *> &passed){

	vector<Tag> tags;
	for (set <Tag *> :: iterator i = passed.begin(); i != passed.end(); i++)
		tags.push_back(**i);
	auto_ptr< vector<File> > files(database->get_files(tags));

	vector<string> *result = new vector<string>;
	string prev = "";
	for (vector<File> :: iterator i = files->begin(); i != files->end(); i++)
		if (i->get_name() != prev){
			prev = i->get_name();
			result->push_back(i->get_name());
		};
	return result;
	
};

vector<string>* FileSystem :: get_deep_children(const string &name, const set<Tag *> &passed){

	vector<Tag> tags;
	for (set <Tag *> :: iterator i = passed.begin(); i != passed.end(); i++)
		tags.push_back(**i);
	auto_ptr< vector<File> > files(database->get_files(tags));
	
	vector<File> :: iterator i;
	for (i = files->begin(); (i != files->end()) && (i->get_name() != name); i++);
	if ((i == files->end()) || ((i + 1)->get_name() != name))
		return NULL;
	vector<string> *result = new vector<string>;
	for (; (i != files->end()) && (i->get_name() == name); i++){
		result->push_back(to_string(i->get_id()) + string(".") + i->get_name());
	}
	return result;

};

int FileSystem :: add_tag(const string &name){
	
	if (this->tags->find(name) != this->tags->end())
		return -1;
	Tag tag(-1, name);
	database->insert(tag);
	(*this->tags)[name] = tag;
	return 0;

};

int FileSystem :: get_real_path(const string &path, string &result){
	
	vector<Tag> tags;
	vector<string> :: iterator i;
	vector<string> split_path;
	
	split(path, split_path);
	for (i = split_path.begin(); (i < split_path.end()) && (*i != FILES_DIR); i++){
		if (this->tags->find(*i) == this->tags->end())
			return -1;
		tags.push_back((*this->tags)[*i]);
	};

	if ((i == split_path.end()) || ((i + 1) == split_path.end()))
		return -1;
	i++;
	if ((i + 1) == split_path.end()){
		File *real_file = database->get_file(tags, i->c_str());
		result = this->files_path + to_string(real_file->get_id());
	}
	else if ((i + 2) == split_path.end()){
		string id = (i + 1)->substr(0, (i + 1)->find(".") - 1);
		result = this->files_path + id;
	}
	else
		return -1;
	return 0;
	
};

int FileSystem :: get_tags(const string &path, vector<Tag> &tags){
	
	vector<string> split_path;
	vector<string> :: iterator i;
	
	split(path, split_path);
	for (i = split_path.begin(); (i < split_path.end()) && (*i != FILES_DIR); i++){
		if (this->tags->find(*i) == this->tags->end()){
			cout << "problem " << *i << endl;
			return -1;
		};
		tags.push_back((*this->tags)[*i]);
	};
	return 0;
};

int FileSystem :: get_file_name(const string &path, string &name){

	vector<string> :: iterator i;
	vector<string> split_path;
	split(path, split_path);

	for (i = split_path.begin(); (i < split_path.end()) && (*i != FILES_DIR); i++);
	if ((i == split_path.end()) || ((i + 1) == split_path.end()))
		return -1;
	name = *(i + 1);
	return 0;

};

int FileSystem :: del_tag(const string &name){
	database->del((*this->tags)[name]);
	int result = this->tags->erase(name);	
	return result == 1 ? 0 : -1;
};

